package com.own.business.question.business.service.impl;

import com.own.business.question.business.service.PracticeHistoryService;
import com.own.business.question.business.service.QuestionOptionService;
import com.own.business.question.business.service.QuestionService;
import com.own.business.question.business.util.QuestionUtil;
import com.own.business.question.common.em.QuestionType;
import com.own.business.question.entity.bo.QuestionBo;
import com.own.business.question.entity.bo.QuestionOptionBo;
import com.own.business.question.entity.map.QuestionMapVo;
import com.own.business.question.entity.po.Question;
import com.own.business.question.entity.po.QuestionOption;
import com.own.business.question.entity.query.QuestionQuery;
import com.own.business.question.entity.vo.QuestionOptionVo;
import com.own.business.question.entity.vo.QuestionVo;
import com.own.business.question.mapper.QuestionMapper;
import com.own.component.common.base.service.impl.AbstractBaseService;
import com.own.component.common.model.PageModel;
import com.own.component.common.model.UpdateModel;
import com.own.component.common.util.MapperUtil;
import com.own.component.common.util.list.ListUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * QuestionServiceImpl
 *
 * @author chenxueli
 * @date 2021/05/14 09:41:05
 */
@Slf4j
@Service
public class QuestionServiceImpl extends AbstractBaseService<
        Question,
        QuestionBo,
        QuestionVo,
        QuestionMapVo,
        QuestionQuery,
        QuestionMapper
        > implements QuestionService {

    @Resource
    private QuestionMapper questionMapper;

    @Resource
    private QuestionOptionService questionOptionService;

    @Resource
    private PracticeHistoryService practiceHistoryService;

    @Resource
    private MapperUtil mapperUtil;

    /**
     * 查询所有的数据
     *
     * @param query 查询条件
     * @return 所有的列表数据
     */
    @Override
    public List<QuestionVo> all(QuestionQuery query) {
        var idList = questionMapper.listIds(query);
        if (idList.size() == 0) {
            return new ArrayList<>();
        }
        query.setIdList(idList);
        var resultList = questionMapper.listByIds(query);
        afterList(resultList, query);
        return resultList;
    }

    /**
     * 分页查询信息
     *
     * @param query 查询条件
     * @return 分页数据
     */
    @Override
    public PageModel<QuestionVo> page(QuestionQuery query) {
        if (query.getRandomNumber() != null) {
            log.warn("分页查询不能有随机题目数量参数，已修正为null");
            query.setRandomNumber(null);
        }
        var pageModel = super.page(query, true);
        afterList(pageModel.getList(), query);
        return pageModel;
    }

    /**
     * 添加信息
     *
     * @param bo 添加对象
     * @return 添加标识
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UpdateModel<QuestionVo> add(QuestionBo bo) {
        var question = bo.toPo();
        // 添加题目主要信息
        add(question);
        // 判断是否是单选或者多选（如果是多选题，需要题目的选项分别存储）
        questionOptionService.addPoBatch(question.getId(), question.getType(), bo.getOptionList());
        // 返回数据
        return new UpdateModel<>(ofVo(question));
    }

    /**
     * 添加信息
     *
     * @param questionBos 添加对象列表
     * @return 添加标识
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UpdateModel<List<QuestionVo>> addBatch(List<QuestionBo> questionBos) {
        var list = questionBos.stream().map(QuestionBo::toPo).toList();
        // 添加题目列表
        addPoBatch(list);
        // 批量插入选项信息
        var optionList = new ArrayList<QuestionOption>();
        for (var i = 0; i < list.size(); i++) {
            var question = list.get(i);
            var bo = questionBos.get(i);
            if (Question.Type.SINGLE_CHOICE == question.getType() || Question.Type.MULTIPLE_CHOICE == question.getType()) {
                var tempOptionList = bo.getOptionList().stream()
                        .map(QuestionOptionBo::toPo)
                        .peek(option -> option.setFkQuestionId(question.getId()))
                        .toList();
                optionList.addAll(tempOptionList);
            }
        }
        // 批量添加选项信息
        questionOptionService.addPoBatch(optionList);
        return new UpdateModel<>(list.stream().map(QuestionVo::new).toList());
    }

    /**
     * 修改信息
     *
     * @param pk 主键id
     * @param bo 修改对象
     * @return 修改标识
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UpdateModel<QuestionVo> update(Long pk, QuestionBo bo) {
        var question = getPoById(pk);
        // 获取是否有选项信息，如果有，先要把之前的选项信息全部删除
        var flag = Question.Type.SINGLE_CHOICE == question.getType() || Question.Type.MULTIPLE_CHOICE == question.getType();
        if (flag) {
            questionOptionService.delete(QuestionOption::getFkQuestionId, pk);
        }
        bo.toPo(question);
        update(question);
        if (flag) {
            questionOptionService.addPoBatch(question.getId(), bo.getOptionList());
        }
        return new UpdateModel<>(new QuestionVo(question));
    }

    /**
     * 删除信息
     *
     * @param pk 主键id
     * @return 删除标识\
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UpdateModel<Boolean> delete(Long pk) {
        return new UpdateModel<>()
                .check(
                        questionOptionService.delete(QuestionOption::getFkQuestionId, pk),
                        super.delete(pk)
                );
    }

    /**
     * 根据id获取信息之后处理的信息
     *
     * @param vo vo对象
     * @return 处理的vo对象\
     */
    @Override
    public QuestionVo afterGetById(QuestionVo vo) {
        // 查询题目的选项信息
        var flag = Question.Type.SINGLE_CHOICE == vo.getType() || Question.Type.MULTIPLE_CHOICE == vo.getType();
        if (flag) {
            var optionList = questionOptionService
                    .listEqual(QuestionOption::getFkQuestionId, vo.getId())
                    .stream()
                    .map(QuestionOptionVo::new)
                    .toList();
            vo.setOptionList(optionList);
        }
        return super.afterGetById(vo);
    }

    /**
     * 根据id获取题目信息（不需要答案，并打乱顺序）
     * \
     *
     * @param questionId 题目id
     * @return 题目信息
     */
    @Override
    public QuestionVo getByIdForAnswer(Long questionId) {
        var vo = getById(questionId);
        QuestionUtil.clearAnswer(vo);
        QuestionUtil.shuffle(vo);
        return vo;
    }

    /**
     * 根据id列表获取题目列表
     *
     * @param questionIdList 题目id列表
     * @param answer         是否需要答案
     * @param shuffle        是否需要打乱
     * @param type           题目类型
     * @param number         题目数量
     * @return 题目列表
     */
    @Override
    public List<QuestionVo> listByIds(List<Long> questionIdList, boolean answer, boolean shuffle, QuestionType type, int number) {
        if (questionIdList.size() == 0) {
            return new ArrayList<>();
        }
        var query = new QuestionQuery();
        query.setQuestionIds(StringUtils.join(questionIdList, ","));
        query.setNeedAnswer(answer);
        query.setNeedShuffle(shuffle);
        if (number > 0) {
            query.setRandomNumber(number);
        }
        if (type != QuestionType.All) {
            query.setType(type.getType());
        }
        return all(query);
    }

    /**
     * 修改题目的可用状态
     *
     * @param pk 题目id
     * @return 修改标识
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UpdateModel<Boolean> updateDisabled(Long pk) {
        var po = getPoById(pk);
        po.setDisabled((po.getDisabled() + 1) % 2);
        return new UpdateModel<>(
                update(po).isSuccess()
        );
    }

    /**
     * 题目答案比对
     *
     * @param pk     题目id
     * @param answer 答案信息
     * @return 比对结果（0-1）
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public BigDecimal matchAnswer(Long pk, String answer) {
        if (StringUtils.isEmpty(answer)) {
            return BigDecimal.ZERO;
        }
        var vo = getById(pk, false);
        if (vo == null) {
            return BigDecimal.ZERO;
        }
        var userKey = answer.toUpperCase();
        var systemKey = vo.getAnswer().toUpperCase();
        switch (vo.getType()) {
            case Question.Type.SINGLE_CHOICE:
            case Question.Type.TRUE_OR_FALSE:
                return userKey.equals(systemKey) ? BigDecimal.ONE : BigDecimal.ZERO;
            case Question.Type.MULTIPLE_CHOICE: {
                if (userKey.equals(systemKey)) {
                    return BigDecimal.ONE;
                }
                // 判断是错选还是多选
                var systemKeyArray = systemKey.split(",");
                var userKeyArray = userKey.split(",");
                if (systemKeyArray.length < userKeyArray.length) {
                    return BigDecimal.ZERO;
                }
                var keyList = new ArrayList<String>();
                for (var key : userKeyArray) {
                    if (Arrays.binarySearch(systemKeyArray, key) >= 0) {
                        keyList.add(key);
                    } else {
                        return BigDecimal.ZERO;
                    }
                }
                if (keyList.size() == 0) {
                    return BigDecimal.ZERO;
                } else if (systemKeyArray.length == keyList.size()) {
                    return BigDecimal.ONE;
                } else {
                    return new BigDecimal("0.5");
                }
            }
            case Question.Type.COMPLETION:
                log.warn("填空题判断");
                return BigDecimal.ZERO;
            case Question.Type.SHORT_ANSWER:
                log.warn("简答题判断");
                return BigDecimal.ZERO;
            default:
                return BigDecimal.ZERO;
        }
    }

    /**
     * 添加总数
     *
     * @param pk 题目id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addTotal(Long pk) {
        questionMapper.addTotal(pk);
    }

    /**
     * 添加总数
     *
     * @param pkList 题目id列表
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addTotal(List<Long> pkList) {
        questionMapper.batchAddTotal(pkList);
    }

    /**
     * 列表查询完后需要的处理
     *
     * @param list  列表信息
     * @param query 查询条件
     */
    private void afterList(List<QuestionVo> list, QuestionQuery query) {
        // 是否需要打乱顺序（默认是不打乱顺序的）
        if (query.getNeedShuffle() != null && query.getNeedShuffle()) {
            list.parallelStream().forEach(QuestionUtil::shuffle);
        }
        // 是否需要删除答案（模式删除答案）
        if (query.getNeedAnswer() == null || !query.getNeedAnswer()) {
            list.parallelStream().forEach(QuestionUtil::clearAnswer);
        }
        // 是否是有题目顺序的
        if (StringUtils.isNotEmpty(query.getQuestionIds())) {
            var questionIdList = ListUtil.Spilt.toLong(query.getQuestionIds());
            ListUtil.Sort.sort(list, questionIdList, QuestionVo::getId);
        }
    }

    /**
     * 根据流水id获取题目信息
     *
     * @param historyId 流水id
     * @return 题目信息
     */
    @Override
    public QuestionVo getByHistoryId(Long historyId) {
        // 获取历史数据信息
        var history = practiceHistoryService.getPoById(historyId);
        // 获取题目列表
        var answer = PracticeServiceImpl.ANSWER_MAP.getOrDefault(history.getStatus(), false);
        var question = getById(history.getFkQuestionId());
        question.setUserAnswer(history.getAnswer());
        // 将题目选项排序
        if (question.getType() == Question.Type.SINGLE_CHOICE || question.getType() == Question.Type.MULTIPLE_CHOICE) {
            var keySortOrderList = ListUtil.Spilt.toInteger(history.getKeySortOrder(), ",");
            QuestionUtil.shuffle(question, keySortOrderList);
            ListUtil.Sort.sort(question.getOptionList(), keySortOrderList, QuestionOptionVo::getSortOrder);
        }
        if (answer) {
            QuestionUtil.clearAnswer(question);
        }
        return question;
    }
}
